package com.xjj.sso.client.filter;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.xjj.sso.client.pojo.RequestParameter;
import com.xjj.sso.client.rabbitmq.QueueConsumer;
import com.xjj.sso.client.session.SessionHandle;
import com.xjj.sso.client.user.SSOReceipt;
import com.xjj.sso.client.util.EncryptUtils;
import com.xjj.sso.client.SSOConstants;
import com.xjj.sso.client.util.SSOUtils;

public class SSOClientFilter implements Filter {
	public static ConcurrentHashMap<String,HttpSession> TICKET_SESSION_CACHE = new ConcurrentHashMap<String,HttpSession>();
	public static ConcurrentHashMap<String,String> SESSION_TICKET_CACHE = new ConcurrentHashMap<String,String>();
	
	@Override
	public void doFilter(ServletRequest req, ServletResponse resp,
			FilterChain chain) throws IOException, ServletException {

		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) resp;
		
		String uri = request.getRequestURI();
		String contextPath = request.getContextPath();
		
		if(SSOUtils.isNotFilter(uri,contextPath))
		{
			chain.doFilter(request, response);
			return;
		}
		
		String requestLogoutTicket = request.getParameter("requestLogout");
		//如果是被动单点退出
		if(SSOUtils.isNotBlank(requestLogoutTicket))
		{
			ssoLogout(requestLogoutTicket);
			return;
		}
		
		//如果是客户端的单点登陆
		if(uri.endsWith(SSOConstants.SSO_CLIENT_SSOLOGONURL))
		{
			ssoLogon(request,response);
			return;
		}

		//如果已经登陆成功
		Object receiptObj = request.getSession().getAttribute(SSOConstants.SSO_CLIENT_RECEIPT);
		if(null != receiptObj)
		{
			chain.doFilter(request, response);
		}else
		{
			//单点登陆验证
			authentication(request, response, chain);
		}
		
	}
	
	
	/**
	 * 单点登陆验证
	 * @param request
	 * @param response
	 * @throws IOException 
	 * @throws ServletException 
	 */
	private void authentication(HttpServletRequest request,HttpServletResponse response,FilterChain chain) throws IOException, ServletException
	{
		String ticket = request.getParameter("ticket");
		
		//还没有ticket，说明还没有验证通过，去sso服务端去验证，并且记录当前请求的参数。
		if(SSOUtils.isBlank(ticket))
		{
			
			Map<String, String[]> pramMap = request.getParameterMap();
			Map<String, String[]> pramMapNew = new HashMap<String, String[]>();
			
			pramMapNew.putAll(pramMap);
			String paramCode = null;
			if(!pramMap.isEmpty())
			{
				String method = request.getMethod();
				paramCode = RequestParameter.generateCode();
				RequestParameter param = new RequestParameter();
				param.setCode(paramCode);
				param.setMethod(method);
				param.setRequestPram(pramMapNew);
				param.setAction(getService(request,null,false));
				
				request.getSession().setAttribute("requestPram"+paramCode, param);
			}
			
			StringBuilder authUrl = new StringBuilder();
			authUrl.append(SSOConstants.SSO_SERVER_AUTHENTICATION);
			authUrl.append("?service=");
			authUrl.append(getService(request,paramCode,true));
			
			authUrl.append("&projectCode=");
			authUrl.append(SSOConstants.SSO_CLIENT_PROJECTCODE);
			
			System.out.println("=====================sso client authentication sendRedirect begin============================");
			System.out.println("authUrl="+authUrl.toString());
			System.out.println("=====================sso client authentication sendRedirect end==============================");
			
			response.sendRedirect(authUrl.toString());
			return;
		}
		else
		{
			//有ticket说明已经从服务端验证通过了，取得登陆信息，并且还原原来的请求场景
			String ssoParamCode = request.getParameter("ssoParamCode");
			String service = getService(request,ssoParamCode,false);
			SSOReceipt receipt = SSOUtils.validateTicket(ticket, service);
			System.out.println("---------------------sso client authentication validateTicket begin---------------------");
			System.out.println("ticket="+ticket);
			System.out.println("service="+service);
			System.out.println("receipt="+receipt);
			System.out.println("---------------------sso client authentication validateTicket end---------------------");
			if(null == receipt)
			{
				
				StringBuilder authUrl = new StringBuilder();
				authUrl.append(SSOConstants.SSO_SERVER_AUTHENTICATION);
				authUrl.append("?service=");
				authUrl.append(getService(request,null,true));
				authUrl.append("&projectCode=");
				authUrl.append(SSOConstants.SSO_CLIENT_PROJECTCODE);
				
				//没有验证通过，再去重新验证。
				response.sendRedirect(authUrl.toString());
				return;
			}
			
			SessionHandle handle;
			try {
				handle = (SessionHandle) (Class.forName(SSOConstants.SSO_CLIENT_SESSIONHANDLE).newInstance());
				handle.handle(request, receipt);
			} catch (Exception e) {
				e.printStackTrace();
			}
			
			TICKET_SESSION_CACHE.put(ticket, request.getSession());
			SESSION_TICKET_CACHE.put(request.getSession().getId(), ticket);
			
			
			//如果没有参数
			if(SSOUtils.isBlank(ssoParamCode))
			{
				chain.doFilter(request, response);
			}else
			{
				Object requestPramObj = request.getSession().getAttribute("requestPram"+ssoParamCode);
				RequestParameter param = (RequestParameter)requestPramObj;
				
				//还原原来的请求场景
				//generateHtmlFormSubmit(param,request,response);
				//生成跳转链接，直接调转，废弃模拟表单提交的方式，因为表单提交的方式会出现不友好的loading页面。
				String ssoUrl = this.generateRedirectUrl(param);
				request.getSession().removeAttribute("requestPram"+ssoParamCode);
				response.sendRedirect(ssoUrl);
				return;
			}
		}
	}
	
	/**
	 * 被动单点退出
	 * @param request
	 * @param response
	 */
	private void ssoLogout(String ticket){
		if(TICKET_SESSION_CACHE.containsKey(ticket))
		{
			HttpSession sess = TICKET_SESSION_CACHE.get(ticket);
			if(null != sess)
			{
				SESSION_TICKET_CACHE.remove(sess.getId());
				sess.invalidate();
			}
			TICKET_SESSION_CACHE.remove(ticket);
		}
	}
	
	/**
	 * 单点登陆(登陆窗口在客户端)
	 * @param request
	 * @param response
	 * @throws IOException 
	 * @throws ServletException 
	 */
	private void ssoLogon(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException{
		
		String ticket = request.getParameter("ticket");
		String ssoerror = request.getParameter(SSOConstants.SSO_SERVER_ERROR);
		//ssoerror不为空，说明已经从sso服务端登陆失败返回来了。
		if(SSOUtils.isNotBlank(ssoerror))
		{
			String ssoParamCode = request.getParameter("ssoParamCode");
			Object requestPramObj = request.getSession().getAttribute("requestPram"+ssoParamCode);
			RequestParameter param = (RequestParameter)requestPramObj;
			
			if(null != SSOConstants.SSO_CLIENT_LOGIN_ISREDIRECT && "false".equals(SSOConstants.SSO_CLIENT_LOGIN_ISREDIRECT))
			{
				
				//request.getSession().removeAttribute("requestPram"+ssoParamCode);
				String loginUrl = request.getContextPath()+SSOConstants.SSO_CLIENT_LOGINURL;
				//如果请求参数为空
				if(null == requestPramObj)
				{
					response.sendRedirect(loginUrl);
					return;
				}
				
				request.setAttribute(SSOConstants.SSO_SERVER_ERROR, ssoerror);
				request.setAttribute("ssoLoginName", param.getParameter("loginName"));
				request.setAttribute("ssoPassword", param.getParameter("password"));
				request.getRequestDispatcher(loginUrl).forward(request, response);
				return;
			}
			else
			{
				StringBuilder loginSB = new StringBuilder(request.getContextPath());
				loginSB.append(SSOConstants.SSO_CLIENT_LOGINURL);
				loginSB.append("?");
				loginSB.append(SSOConstants.SSO_SERVER_ERROR);
				loginSB.append("=");
				loginSB.append(ssoerror);
				
				loginSB.append("&ssoParamCode=");
				loginSB.append(ssoParamCode);
				
				//request.getSession().removeAttribute("requestPram"+ssoParamCode);
				response.sendRedirect(loginSB.toString());
				return;
			}
		}
		
		//如果还没有验证
		if(SSOUtils.isBlank(ticket))
		{
			//接收相关参数(必须的参数)传统登陆方式
			String identity = request.getParameter("identity");
			String password = request.getParameter("password");
			String projectCode = SSOConstants.SSO_CLIENT_PROJECTCODE;
			String backURL = request.getParameter("backURL");
			
			
			if(SSOUtils.isBlank(identity) || SSOUtils.isBlank(password))
			{
				//不符合以上登陆方式，登陆失败
				sendRedirect(request,response,SSOConstants.SSO_CLIENT_LOGINURL);
				return;
			}
			
			
			String paramCode = RequestParameter.generateCode();
			RequestParameter param = new RequestParameter();
			
			//拼装参数信息
			Map<String, String[]> pramMap = new HashMap<String, String[]>();
			//密码加密传输
			password = EncryptUtils.MD5Encode(password);
			
			if(!SSOUtils.isBlank(identity))
			{
				pramMap.put("identity", new String[]{identity});
			}
			if(!SSOUtils.isBlank(password))
			{
				pramMap.put("password", new String[]{password});
			}
			
			pramMap.put("projectCode", new String[]{projectCode});
			pramMap.put("service", new String[]{getService(request,paramCode,false)});
			pramMap.put("backURL", new String[]{backURL});
			
			param.setCode(paramCode);
			param.setMethod("post");
			param.setRequestPram(pramMap);
			param.setAction(SSOConstants.SSO_SERVER_SIGNIN);
			
			
			request.getSession().setAttribute("requestPram"+paramCode, param);
			
			//拼装表单提交
			//generateHtmlFormSubmit(param,request,response);
			
			//生成跳转链接，直接调转，废弃模拟表单提交的方式，因为表单提交的方式会出现不友好的loading页面。
			String ssoUrl = this.generateRedirectUrl(param);
			response.sendRedirect(ssoUrl);
			return;
		}
		//经过上面二层的判断，ticket不为空，说明已经从服务端验证通过了
		String ssoParamCode = request.getParameter("ssoParamCode");
		String service = getService(request,ssoParamCode,false);
		SSOReceipt receipt = SSOUtils.validateTicket(ticket, service);
		Object requestPramObj = request.getSession().getAttribute("requestPram"+ssoParamCode);
		request.getSession().removeAttribute("requestPram"+ssoParamCode);
		//如果请求参数为空
		if(null == requestPramObj)
		{
			sendRedirect(request,response,SSOConstants.SSO_CLIENT_LOGINURL);
			return;
		}
		
		RequestParameter param = (RequestParameter)requestPramObj;

		//验证失败
		if(null == receipt)
		{
			String loginName = param.getParameter("loginName");
			String password = param.getParameter("password"); 
			request.setAttribute(SSOConstants.SSO_SERVER_ERROR,"ticketerror");
			request.setAttribute("loginName",loginName);
			request.setAttribute("password",password);
			request.getRequestDispatcher(SSOConstants.SSO_CLIENT_LOGINURL).forward(request, response);
			return;
		}
		
		SessionHandle handle;
		try {
			handle = (SessionHandle) (Class.forName(SSOConstants.SSO_CLIENT_SESSIONHANDLE).newInstance());
			handle.handle(request, receipt);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		//缓存ticket 和 session 
		TICKET_SESSION_CACHE.put(ticket, request.getSession());
		SESSION_TICKET_CACHE.put(request.getSession().getId(), ticket);
		
		
		//转向成功地址
		String backURL = param.getParameter("backURL");
		
		if(SSOUtils.isNotBlank(backURL))
		{
			sendRedirect(request,response,backURL);
		}else
		{
			sendRedirect(request,response,SSOConstants.SSO_CLIENT_LOGINURL);
		}
	}
	
	/**
	 * 获得service
	 * @param request
	 * @param paramCode
	 * @param encode
	 * @return
	 * @throws UnsupportedEncodingException
	 */
    private String getService(HttpServletRequest request,String paramCode,boolean encode) throws UnsupportedEncodingException{
        StringBuffer sb = new StringBuffer();
        
        sb.append(request.getScheme()+"://");
        
        if(null == SSOConstants.SSO_CLIENT_SERVERNAME || "".equals(SSOConstants.SSO_CLIENT_SERVERNAME))
        {
        	sb.append(request.getServerName());
        }
        else
        {
        	sb.append(SSOConstants.SSO_CLIENT_SERVERNAME);
        }
        
		if(!"80".equals(request.getServerPort()))
		{
			sb.append(":");
			sb.append(request.getServerPort());
		}
        sb.append(request.getRequestURI());
        if(SSOUtils.isNotBlank(paramCode))
        {
        	sb.append("?ssoParamCode="+paramCode);
        }
        
        if(encode)
        {
        	String encodedService = URLEncoder.encode(sb.toString(),"utf-8");
        	return encodedService;
        }else
        {
        	
        	return sb.toString();
        }
        
    }
    
    
    /**
	 * 获得service
	 * @param request
	 * @param paramCode
	 * @param encode
	 * @return
	 * @throws UnsupportedEncodingException
	 */
    private String getService_bak(HttpServletRequest request,String paramCode,boolean encode) throws UnsupportedEncodingException{
        StringBuffer sb = new StringBuffer();
        sb.append(SSOConstants.HTTP_PRE);
        sb.append(SSOConstants.SSO_CLIENT_SERVERNAME);
        sb.append(request.getRequestURI());
        if(SSOUtils.isNotBlank(paramCode))
        {
        	sb.append("?ssoParamCode="+paramCode);
        }
        
        if(encode)
        {
        	String encodedService = URLEncoder.encode(sb.toString(),"utf-8");
        	return encodedService;
        }else
        {
        	return sb.toString();
        }
        
    }
    
    
    
    
    
    /**生成重定向url链接
	 * 
	 * @param param
	 * @return
	 */
	private String generateRedirectUrl(RequestParameter param)
	{
		StringBuilder urlBuff = new StringBuilder();
		urlBuff.append(param.getAction());
		
		Set<String> paramSet = param.getRequestPram().keySet();
		String paramName = null;
		String[] paramValues = null;
		int idx = 0;
		for (Iterator<String> iterator = paramSet.iterator(); iterator.hasNext();) {
			paramName = iterator.next();
			paramValues = param.getRequestPram().get(paramName);
			for(int i=0;i<paramValues.length;i++)
			{
				if(i == 0 && idx ==0 && param.getAction().indexOf("?")<0)
				{
					urlBuff.append("?");
				}else
				{
					urlBuff.append("&");
				}
				urlBuff.append(paramName);
				urlBuff.append("=");
				if (paramValues[i].contains("http")
						|| paramValues[i].contains("#")
						|| paramValues[i].contains("=")
						|| paramValues[i].contains("&")
						|| paramValues[i].contains("+")
						|| paramValues[i].contains(".")
						|| paramValues[i].contains("%"))
				{
					try {
						urlBuff.append(java.net.URLEncoder.encode(paramValues[i],"utf-8"));
						
					} catch (UnsupportedEncodingException e) {
						e.printStackTrace();
					}
				}else
				{
					urlBuff.append(paramValues[i]);
				}
			}
			idx ++;
		}
		return urlBuff.toString();
	}
    
    /**
	 * 根据相关参数生成表单提交html代码
	 * @param param
	 * @param request
	 * @param response
	 * @throws IOException
	 */
	private void generateHtmlFormSubmit(RequestParameter param,HttpServletRequest request,HttpServletResponse response) throws IOException
	{
		response.setContentType("text/html");
		response.setCharacterEncoding("UTF-8");
		PrintWriter out = response.getWriter();
		out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
		out.println("<HTML>");
		out.println("<HEAD>");
		out.println("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">");
		out.println("<TITLE>loading...</TITLE>");
		out.println("<script type=\"text/javascript\"> ");
		out.println("var i = 0;");
		out.println("var s = \"loading\";");
		out.println("function addPoint(){ ");
		out.println("if(i<6){");
		out.println("s += \".\";");
		out.println("document.getElementById(\"point\").innerHTML = s;");
		out.println("i++; ");
		out.println("}else{  s = \"loading\";");
		out.println("document.getElementById(\"point\").innerHTML = s;");
		out.println("i = 0; ");
		
		out.println("}}");
		out.println("function loadPoint(){ window.setInterval(\"addPoint()\",200);}");
		out.println("</script>");
		out.println("</HEAD>");
		
		out.println("  <BODY onload=\"loadPoint();\">");
		out.println("<form action=\""+param.getAction()+"\" method=\""+param.getMethod()+"\">");
		
		Set<String> paramSet = param.getRequestPram().keySet();
		String paramName = null;
		String[] paramValues = null;
		for (Iterator<String> iterator = paramSet.iterator(); iterator.hasNext();) {
			paramName = iterator.next();
			paramValues = param.getRequestPram().get(paramName);
			for(int i=0;i<paramValues.length;i++)
			{
				out.println("  <INPUT TYPE=\"hidden\" NAME=\""+paramName+"\" value=\""+paramValues[i]+"\">");
			}
		}
    	out.println("  </form>");
    	
    	//添加等待loading begin
    	out.println("<br/><br/><br/><br/><br/><br/><div style=\"width:960px;margin:0 auto;height:auto\">");
    	out.println("<p style=\"text-align:center;\" id=\"point\">");
    	out.println("loading......");
    	out.println("</p></div>");
    	//添加等待loading end
    	
		out.println("  </BODY>");
		out.println("</HTML>");
		out.println("<script type=\"text/javascript\"> ");
		out.println("window.document.forms[0].submit();");
		out.println("</script>");
		out.flush();
		out.close();
		
	}
	
	
	/**
	 * 客户端跳转
	 * @param request
	 * @param response
	 * @param url
	 * @throws IOException
	 */
	private void sendRedirect(HttpServletRequest request,HttpServletResponse response,String url) throws IOException
	{
		if(SSOUtils.isBlank(url))
		{
			return;
		}
		
		if(url.startsWith(SSOConstants.HTTP_PRE) || url.startsWith(SSOConstants.HTTPS_PRE))
		{
			response.sendRedirect(url);
			return;
		}
		
		response.sendRedirect(request.getContextPath()+url);
	}
	
	
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		//如果通知类型为rabbitmq
		if(SSOConstants.SSO_NOTIFICATION_TYPE.equals("rabbitmq"))
		{
			try {
				//启动mq 消费线程
				QueueConsumer consumer = new QueueConsumer("sso.*");
		        Thread consumerThread = new Thread(consumer);
		        consumerThread.start();
			} catch (Exception e) {
				
				System.out.println("启动mq线程报错");
				e.printStackTrace();
			}
		}
	}	
	@Override
	public void destroy() {
		
	}
}